#!/usr/bin/env python

from rdflib import Graph, URIRef, Literal, RDF, RDFS, Namespace
import sys
import json
import urllib
import urllib2
import lxml.html
import re
import subprocess

BASEURI = "http://data.aalto.fi/id/courses/noppa/"
LANGS = ['fi', 'sv', 'en']

NOPPA = Namespace(BASEURI)
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
SKOS = Namespace("http://www.w3.org/2004/02/skos/core#")
ORG = Namespace("http://www.w3.org/ns/org#")
DCT = Namespace("http://purl.org/dc/terms/")
FOAF = Namespace("http://xmlns.com/foaf/0.1/")
ICAL = Namespace("http://www.w3.org/2002/12/cal/icaltzd#")
AIISO = Namespace("http://purl.org/vocab/aiiso/schema#")
TEACH = Namespace("http://linkedscience.org/teach/ns#")

APIKEY = "cdda4ae4833c0114005de5b5c4371bb8"
BASEURL = "http://noppa-api-dev.aalto.fi/api/v1/"

NOPPADEVURL = "http://noppa-api-dev.aalto.fi/"
NOPPAURL = "https://noppa.aalto.fi/"

def api_call(method=None, url=None, **kwargs):
  if not url:
    url = BASEURL + urllib.quote(method)
    if kwargs: url += "?" + urllib.urlencode(kwargs)
#  print >>sys.stderr, url
  req = urllib2.Request(url, None, {'Key':APIKEY})
  resp = urllib2.urlopen(req)
  data = json.load(resp)
  resp.close()
  return data

def url2uri(url):
  return URIRef(url.replace(BASEURL, BASEURI))

def get_link(links, linktype):
  for link in links:
    if link['title'] == linktype:
      return link['uri']

def html2text(htmlstr):
  html = lxml.html.fragment_fromstring(htmlstr, create_parent=True) # parse
  text = html.text_content().strip() # convert to text
  return re.sub(r'\s+', ' ', text) # normalize whitespace

tzcache = {}
def infer_timezone(datestr):
  "infer timezone (e.g. '+03:00'), using command line date utility"
  # NOTE: this assumes that the system timezone rules are same as Aalto's
  if datestr not in tzcache:
    tzcache[datestr] = subprocess.Popen(
      ['date', '-d', datestr,'+%:z'],
      stdout=subprocess.PIPE
    ).communicate()[0].strip()
  return tzcache[datestr]
                            

g = Graph()
g.namespace_manager.bind('noppa', NOPPA)
g.namespace_manager.bind('xsd', XSD)
g.namespace_manager.bind('skos', SKOS)
g.namespace_manager.bind('org', ORG)
g.namespace_manager.bind('dct', DCT)
g.namespace_manager.bind('foaf', FOAF)
g.namespace_manager.bind('ical', ICAL)
g.namespace_manager.bind('aiiso', AIISO)
g.namespace_manager.bind('teach', TEACH)

orgs = api_call('organizations')
for org in orgs:
  orguri = NOPPA['org_' + org['org_id']]
  g.add((orguri, RDF.type, AIISO.School))
  g.add((orguri, AIISO.code, Literal(org['org_id'])))
  for lang in LANGS:
    g.add((orguri, FOAF.name, Literal(org['name_%s' % lang], lang)))
  depts = api_call(url=get_link(org['links'], 'departments'))
  for dept in depts:
    depturi = NOPPA['dept_' + dept['dept_id']]
    g.add((depturi, RDF.type, AIISO.Department))
    g.add((depturi, AIISO.code, Literal(dept['dept_id'])))
    g.add((depturi, AIISO.part_of, orguri))
    g.add((orguri, AIISO.organization, depturi)) # inverse of above
    for lang in LANGS:
      g.add((depturi, FOAF.name, Literal(dept['name_%s' % lang], lang)))
    courses = api_call(url=get_link(dept['links'], 'courses'))
    for course in courses:
      courseuri = NOPPA['course_' + course['course_id']]
      g.add((courseuri, RDF.type, AIISO.Course))
      g.add((courseuri, AIISO.code, Literal(course['course_id'])))
      g.add((depturi, AIISO.teaches, courseuri))
      url = course['course_url'].replace(NOPPADEVURL, NOPPAURL)
      g.add((courseuri, FOAF.homepage, URIRef(url)))
      lang = course['noppa_language']
      g.add((courseuri, DCT.language, Literal(lang)))
      g.add((courseuri, TEACH.courseTitle, Literal(course['name'], lang)))
      # request course overview data
      overview = api_call('courses/%s/overview' % course['course_id'])
      if 'content' in overview:
        g.add((courseuri, TEACH.courseDescription,
          Literal(html2text(overview['content']), lang)))
      if 'credits' in overview:
        g.add((courseuri, TEACH.ects, Literal(html2text(overview['credits']))))
      if 'teaching_period' in overview:
        g.add((courseuri, TEACH.academicTerm,
          Literal(html2text(overview['teaching_period']))))
      if 'grading_scale' in overview:
        g.add((courseuri, TEACH.grading, Literal(html2text(overview['grading_scale']))))
      # request assignment data
      try:
        assignments = api_call('courses/%s/assignments' % course['course_id'])
      except urllib2.HTTPError:
        print >>sys.stderr, "HTTP error fetching assignments data for course " + course['course_id']
        lectures = []
      for idx,assignment in enumerate(assignments):
        asguri = NOPPA['assignment_%s_%d' % (course['course_id'], idx)]
        g.add((asguri, RDF.type, TEACH.Assignment))
        g.add((courseuri, TEACH.hasAssignment, asguri))
        if 'title' in assignment:
          g.add((asguri, TEACH.hasTitle, Literal(assignment['title'], lang)))
        if 'content' in assignment:
          g.add((asguri, TEACH.hasDescription, Literal(assignment['content'], lang)))
        if 'deadline' in assignment:
          tz = infer_timezone(assignment['deadline'])
          g.add((asguri, TEACH.deadline, Literal(assignment['deadline']+tz, datatype=XSD.dateTime)))
        if 'material' in assignment:
          for material in assignment['material']:
            maturi = URIRef(material['url'].replace(NOPPADEVURL, NOPPAURL))
            g.add((maturi, RDF.type, TEACH.Material))
            g.add((asguri, TEACH.hasAssignmentMaterial, maturi))
            if 'title' in material:
              g.add((maturi, TEACH.hasTitle, Literal(material['title'], lang)))
      # request lecture data
      try:
        lectures = api_call('courses/%s/lectures' % course['course_id'])
      except urllib2.HTTPError:
        print >>sys.stderr, "HTTP error fetching lectures data for course " + course['course_id']
        lectures = []
      for lecture in lectures:
        lecturi = NOPPA['lecture_' + lecture['lecture_id']]
        g.add((lecturi, RDF.type, ICAL.Vevent))
        g.add((courseuri, TEACH.arrangedAt, lecturi))
        tz = infer_timezone(lecture['date'])
        dtstart = lecture['date'] + "T" + lecture['start_time'] + tz
        dtend = lecture['date'] + "T" + lecture['end_time'] + tz
        g.add((lecturi, ICAL.dtstart, Literal(dtstart, datatype=XSD.dateTime)))
        g.add((lecturi, ICAL.dtend, Literal(dtend, datatype=XSD.dateTime)))
        if 'location' in lecture:
          g.add((lecturi, TEACH.room, Literal(lecture['location'])))
        if 'title' in lecture:
          g.add((lecturi, ICAL.summary, Literal(lecture['title'], lang)))
        if 'content' in lecture:
          g.add((lecturi, RDFS.comment, Literal(lecture['content'], lang)))
        
g.serialize(destination=sys.stdout, format='n3')
